import { Logger } from "./Logger";
import { LoggerManager } from "./LoggerManager";

/**
 * 函数调用日志
 */
function LogCallWithType(type: number = Logger.DEFAULT_TYPE): MethodDecorator {
  return (target: any, methodName: string, propertyDescriptor: PropertyDescriptor) => {
    const method = propertyDescriptor.value;
    const logger = LoggerManager.getLogger(target, type);
    if (method === undefined || typeof target[methodName] !== "function") {
      logger.error("Cannot access to method: [%s], log failed.", methodName);
    } else {
      propertyDescriptor.value = function (...args: any[]) {
        if (args.length == 0) {
          logger.debug("Method: [%s] called with no arguments", methodName);
        } else {
          logger.debug("Method: [%s] called with arguments %j", methodName, args);
        }
        try {
          return method.apply(this, args);
        } catch (e) {
          logger.error("Method execute failed with error: %j", e);
          throw e;
        }
      }
    }
  }
}

export const LogCall: MethodDecorator & ((type?: number) => MethodDecorator) = function (target?: any, methodName?: string, descriptor?: PropertyDescriptor) {
  // 检查是否是直接装饰器调用(不带括号)还是工厂函数调用(带括号)
  if (arguments.length === 1 && typeof target === 'number') {
    // 工厂函数调用 @LogCall(type)
    return LogCallWithType(target);
  } else if (arguments.length === 3 && typeof target === 'object' && typeof methodName === 'string') {
    // 直接装饰器调用 @LogCall
    // 创建一个临时的装饰器工厂并立即调用它
    const decorator = LogCallWithType();
    return decorator(target, methodName, descriptor!);
  } else if (arguments.length === 0) {
    // 工厂函数调用 @LogCall()
    return LogCallWithType();
  } else {
    // 默认情况，使用默认类型
    return LogCallWithType();
  }
} as any;

/**
 * 函数执行结果日志
 */
function LogResultWithType(type: number = Logger.DEFAULT_TYPE): MethodDecorator {
  return (target: any, methodName: string, propertyDescriptor: PropertyDescriptor) => {
    const method = propertyDescriptor.value;
    const logger = LoggerManager.getLogger(target, type);
    if (method === undefined) {
      logger.error("Cannot access to method: [%s], log failed.", methodName);
    } else {
      propertyDescriptor.value = function (...args: any[]) {
        try {
          const result = method.apply(this, args);
          if (result instanceof Promise) {
            result.then(result => {
              let type = typeof result === 'object' ? '%j' : '%s'
              logger.debug("Method [%s] finish with result: " + type, methodName, type == '%j' ? result : result + '');
              return result;
            }).catch((e) => {
              logger.error("Method [%s] finish with error: %j", methodName, e);
              throw e;
            })
          } else {
            let type = typeof result === 'object' ? '%j' : '%s'
            logger.debug("Method [%s] finish with result: " + type, methodName, type == '%j' ? result : result + '');
          }

          return result;
        } catch (e) {
          logger.error("Method [%s] finish with error: %j", methodName, e);
          throw e;
        }
      }
    }
  }
}

export const LogResult: MethodDecorator & ((type: number) => MethodDecorator) = function (target?: any, methodName?: string, descriptor?: PropertyDescriptor) {
  // 检查是否是直接装饰器调用(不带括号)还是工厂函数调用(带括号)
  if (arguments.length === 1 && typeof target === 'number') {
    // 工厂函数调用 @LogCall(type)
    return LogResultWithType(target);
  } else if (arguments.length === 3 && typeof target === 'object' && typeof methodName === 'string') {
    // 直接装饰器调用 @LogCall
    // 创建一个临时的装饰器工厂并立即调用它
    const decorator = LogResultWithType();
    return decorator(target, methodName, descriptor!);
  } else if (arguments.length === 0) {
    // 工厂函数调用 @LogCall()
    return LogResultWithType();
  } else {
    // 默认情况，使用默认类型
    return LogResultWithType();
  }
} as any;

/**
 * 模板字符串日志
 */
export function LogTemplate(type: number = Logger.DEFAULT_TYPE) {
  return (strings: TemplateStringsArray, ...args: any[]) => {
    const logger = LoggerManager.getLogger('anonymous', type);
    logger.info("built with format: %j and args: %j", strings, args);
    return strings[0] + args.map((e, i) => `${e}${strings[i+1]}`).join('');
  }
}